﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using DomainDriver.DomainModeling.DataProviders;
using DomainDriver.DomainModeling.Repositories;
using DomainDriver.DomainModeling.StorageManagers;
using DomainDriver.DomainModeling.StorageManagers.DataSetStorage;
using DomainDriver.DomainModeling.StorageManagers.EntityFrameworkStorage;

namespace DomainDriver.DomainModeling.DomainModels.ConstructionResources
{
    public class RepositoryConstructionMethods
    {
        public static object CreateTransientBackedRepository(Type dotNetKeyType, Type databaseKeyType, Type domainObjectType, IDataProvider dataProvider, Expression domainObjectConstructionMethod, IDomainModel domainModel)
        {
            Type genericRepositoryType = typeof(Repository<,>);
            Type genericStorageManagerInterfaceType = typeof(IStorageManager<,>);
            Type genericStorageManagerType = typeof(TransientStorageManager<,>);
            Type dataProviderType = typeof(IDataProvider);
            Type dataSourceType = dataProvider.DataSourceType;

            // Get IStorageManagerType
            Type[] genericStorageManagerInterfaceParameters = new Type[] { dotNetKeyType, domainObjectType };
            Type storageManagerInterfaceType = genericStorageManagerInterfaceType.MakeGenericType(genericStorageManagerInterfaceParameters);

            // Construct Storage Manager
            Type[] genericStorageManagerParameters = new Type[] { dotNetKeyType, domainObjectType };
            Type storageManagerType = genericStorageManagerType.MakeGenericType(genericStorageManagerParameters);

            Type[] storageManagerConstructorArgs = new Type[] { dataProviderType };
            ConstructorInfo storageManagerConstructor = storageManagerType.GetConstructor(storageManagerConstructorArgs);

            object storageManagerInstance = storageManagerConstructor.Invoke(new object[] { dataProvider });

            // Construct Repository
            Type[] genericRepositoryParameters = new Type[] { dotNetKeyType, domainObjectType };
            Type repositoryType = genericRepositoryType.MakeGenericType(genericRepositoryParameters);

            Type[] repositoryConstructorArgs = new Type[] { storageManagerInterfaceType };
            ConstructorInfo repositoryConstructor = repositoryType.GetConstructor(repositoryConstructorArgs);

            object repositoryInstance = repositoryConstructor.Invoke(new object[] { storageManagerInstance });
            return repositoryInstance;
        }

        public static object CreateDataSetBackedRepository(Type dotNetKeyType, Type databaseKeyType, Type domainObjectType, IDataProvider dataProvider, Expression domainObjectConstructionMethod, IDomainModel domainModel)
        {
            Type genericRepositoryType = typeof(Repository<,>);
            Type genericStorageManagerInterfaceType = typeof(IStorageManager<,>);
            Type genericDataSetSerializableInterfaceType = typeof(IDataSetSerializable<,>);
            Type genericStorageManagerType = typeof(DataSetFileStorageManager<,,>);
            Type genericDomainObjectConstructionMethodType = typeof(Func<>);
            Type genericDomainObjectConstructionExpressionType = typeof(Expression<>);
            Type dataProviderType = typeof(IDataProvider);
            Type dataSourceType = dataProvider.DataSourceType;
            Type dataSetType = typeof(DataSet);
            Type dataRowType = typeof(DataRow);

            // Get IStorageManager Type
            Type[] genericStorageManagerInterfaceParameters = new Type[] { dotNetKeyType, domainObjectType };
            Type storageManagerInterfaceType = genericStorageManagerInterfaceType.MakeGenericType(genericStorageManagerInterfaceParameters);

            // Get DomainObject construction method Type
            Type[] genericDomainObjectConstructionMethodParameters = new Type[] { domainObjectType };
            Type domainObjectConstructionMethodType = genericDomainObjectConstructionMethodType.MakeGenericType(genericDomainObjectConstructionMethodParameters);

            // Get DomainObject construction Expression Type
            Type[] genericDomainObjectConstructionExpressionParameters = new Type[] { domainObjectConstructionMethodType };
            Type domainObjectConstructionExpressionType = genericDomainObjectConstructionExpressionType.MakeGenericType(genericDomainObjectConstructionExpressionParameters);

            Type dataSetTypeToUse = dataSourceType;
            Type[] genericDataSetSerializableInterfaceParameters = new Type[] { dataSourceType, dataRowType };
            Type dataSetSerializableInterfaceType = genericDataSetSerializableInterfaceType.MakeGenericType(genericDataSetSerializableInterfaceParameters);
            if (!domainObjectType.GetInterfaces().Contains<Type>(dataSetSerializableInterfaceType))
            { dataSetTypeToUse = dataSetType; }

            // Construct Storage Manager
            Type[] genericStorageManagerParameters = new Type[] { dotNetKeyType, domainObjectType, dataSetTypeToUse };
            Type storageManagerType = genericStorageManagerType.MakeGenericType(genericStorageManagerParameters);

            Type[] storageManagerConstructorArgs = new Type[] { dataProviderType, domainObjectConstructionExpressionType, typeof(IBatchPersistableDomainModel) };
            ConstructorInfo storageManagerConstructor = storageManagerType.GetConstructor(storageManagerConstructorArgs);

            object storageManagerInstance = storageManagerConstructor.Invoke(new object[] { dataProvider, domainObjectConstructionMethod, domainModel as IBatchPersistableDomainModel });

            // Construct Repository
            Type[] genericRepositoryParameters = new Type[] { dotNetKeyType, domainObjectType };
            Type repositoryType = genericRepositoryType.MakeGenericType(genericRepositoryParameters);

            Type[] repositoryConstructorArgs = new Type[] { storageManagerInterfaceType };
            ConstructorInfo repositoryConstructor = repositoryType.GetConstructor(repositoryConstructorArgs);

            object repositoryInstance = repositoryConstructor.Invoke(new object[] { storageManagerInstance });
            return repositoryInstance;
        }

        public static object CreateEntityFrameworkBackedRepository(Type dotNetKeyType, Type databaseKeyType, Type domainObjectType, IDataProvider dataProvider, Expression domainObjectConstructionMethod, IDomainModel domainModel)
        {
            Type genericRepositoryType = typeof(Repository<,>);
            Type genericStorageManagerInterfaceType = typeof(IStorageManager<,>);
            Type genericStorageManagerType = typeof(EntityFrameworkDatabaseStorageManager<,,,>);
            Type dataProviderType = typeof(IDataProvider);
            Type dataSourceType = dataProvider.DataSourceType;

            // Get IStorageManager Type
            Type[] genericStorageManagerInterfaceParameters = new Type[] { dotNetKeyType, domainObjectType };
            Type storageManagerInterfaceType = genericStorageManagerInterfaceType.MakeGenericType(genericStorageManagerInterfaceParameters);

            // Construct Storage Manager
            Type[] genericStorageManagerParameters = new Type[] { dotNetKeyType, domainObjectType, databaseKeyType, dataSourceType };
            Type storageManagerType = genericStorageManagerType.MakeGenericType(genericStorageManagerParameters);

            Type[] storageManagerConstructorArgs = new Type[] { dataProviderType };
            ConstructorInfo storageManagerConstructor = storageManagerType.GetConstructor(storageManagerConstructorArgs);

            object storageManagerInstance = storageManagerConstructor.Invoke(new object[] { dataProvider });

            // Construct Repository
            Type[] genericRepositoryParameters = new Type[] { dotNetKeyType, domainObjectType };
            Type repositoryType = genericRepositoryType.MakeGenericType(genericRepositoryParameters);

            Type[] repositoryConstructorArgs = new Type[] { storageManagerInterfaceType };
            ConstructorInfo repositoryConstructor = repositoryType.GetConstructor(repositoryConstructorArgs);

            object repositoryInstance = repositoryConstructor.Invoke(new object[] { storageManagerInstance });
            return repositoryInstance;
        }
    }
}